package simple.tree;

import struct.TreeNode;

/**
 * <a href="https://leetcode.cn/problems/count-complete-tree-nodes/description/">222. 完全二叉树的节点个数</a>
 * 给你一棵 完全二叉树 的根节点 root ，求出该树的节点个数。
 * 完全二叉树 的定义如下：
 *   在完全二叉树中，除了最底层节点可能没填满外，其余每层节点数都达到最大值，并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层，则该层包含 1~ 2h 个节点。
 * 示例 1：
 *   输入：root = [1,2,3,4,5,6]
 *   输出：6
 * 示例 2：
 *   输入：root = []
 *   输出：0
 * 示例 3：
 *   输入：root = [1]
 *   输出：1
 * 提示：
 *   树中节点的数目范围是[0, 5 * 10^4]
 *   0 <= Node.val <= 5 * 10^4
 *   题目数据保证输入的树是 完全二叉树
 * 进阶：遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗？
 * @author 刘学松
 * @date 2023-10-26 9:51
 */
public class 完全二叉树的节点个数 {
    public int countNodes(TreeNode root) {
        if (root == null) {
            return 0;
        }
        TreeNode node = root;
        int level = 0;
        while (node.left != null) {
            level++;
            node = node.left;
        }

        int low = 1 << level, high = (1 << (level + 1)) - 1;
        // 二分查找
        while (low < high) {
            int mid = (high + low + 1) / 2;
            // 如果mid位置有节点，则往右查找，否则往左
            if (exist(level, mid, root)) {
                low = mid;
            } else {
                high = mid - 1;
            }
        }
        return low;
    }

    public boolean exist(int level, int mid, TreeNode node) {
        // 判断0和1来选择左子节点还是右子节点，减1是因为要从mid的二进制第二位开始判断，因为第一位始终为1
        int mask = 1 << (level - 1);
        // 从高位到低位判断二进制，遇到0向左，1向右
        while (node != null && mask > 0) {
            if ((mask & mid) == 0) {
                node = node.left;
            } else {
                node = node.right;
            }
            mask >>= 1;
        }
        return node != null;
    }
}
